/*
 * WLAN State and Scan functions: display list of available WLAN
 * networks, and allow the user to connect to a network, and
 * handle wlan state transitions (connected, not connected etc).
 */

/*===========================================================================*/
/*==============  Includes  =================================================*/
/*===========================================================================*/

#include <stdio.h>
#include <assert.h>
#include <ctype.h>

#include <spr.h>
#include <debuglog.h>
#include <hw.h>
#include <ui.h>
#include <nethwApi.h>
#include <display.h>
#include <lang.h>
#include <langErr.h>
#include <config.h>
#include <configPriority.h>
#include <mainthread.h>
#include <wlanProfile.h>
#include <wlanScan.h>
#include <simpleconfig.h>
#if CFG_VOIP_VOICE_MAIL_ENABLE()
#include <callCtrl.h>
#endif      // CFG_VOIP_VOICE_MAIL_ENABLE()

/*===========================================================================*/
/*==============  Data Definitions/Structures ===============================*/
/*===========================================================================*/

/* If we don't get a scan in a while, report all scan results at once.
 * Note that scans sometimes take a very long time... 
 * FIX: this should perhaps
 * be a hardware configuration instead.
 */
#define UI_WLAN_SCAN_TIMEOUT_SECONDS 40

/* Show new results this often: */
#define UI_WLAN_SCAN_SCREEN_REFRESH_SECONDS 2

/* When showing WPS results, timeout if
 * user doesn't select within this time period:
 * (should be LESS than the 120 period specified).
 */
#define UI_WLAN_SCAN_WPS_SELECT_TIMEOUT_SECONDS 100

/* State data */
struct uiWlanState
{
    /*
     * Current list of visible networks
     * Those that are available are copied to networkCopy[i],
     * with networkP[i] pointing to the copy.
     */
    unsigned networkCount;
    nethwWlanNetworkInfo *networkP[CFG_WLAN_SCAN_MAX];
    nethwWlanNetworkInfo networkCopy[CFG_WLAN_SCAN_MAX];

    /*
     * Tasks that are called whenever a WLAN event
     * occurs
     */
    struct pollerTask stateEventTask;   /* change in wlan state */
    struct pollerTask scanEventTask;    /* change in scan list */
    struct pollerTask iconTimerTask;    /* periodically */
    struct pollerTask wpsChoiceTimerTask; /* timeout if user doesn't select*/

    /* Timeout if we aren't getting a scan soon... */
    struct pollerTask scanTimeoutTask;

    /* Force frequent update of status display: */
    struct pollerTask screenRefreshTask;

    /*
     * WLAN connection state
     */
    int connected;                      /* 1/0 -> conn / not conn */
    char connSSID[WLAN_SSID_LEN + 1];   /* ssid we are connected to */
    unsigned char connBSSID[6];         /* bssid we are connected to */
    short connFreq;                     /* channel freq */
    short connRssi;                     /* RSSI in dB */
    short connRssiIcon;                 /* Current icon setting */

    /*
     * Menu navigation state
     */
    UIMenu *scanMenuHandle;             /* of active scan menu */
    int doScanning;                     /* whether we want to be scanning */
    int menuNetCurr;                    /* index of curr displayed network */
    #if CFG_WLAN_SIMPLE_CONFIG() 
    int SimpleConfigMode;               /* if we show only WPS APs */
        #define uiWlanState_SimpleConfigMode_eOff 0     /* must be zero */
        #define uiWlanState_SimpleConfigMode_ePushButton 1
        #define uiWlanState_SimpleConfigMode_ePIN 2
    char SimpleConfigPin[12];   /* secret number to use for WPS PIN mode */
    #endif

    /*
     * Used for jump straight to letters starting with that name
     */
    unsigned char lastKeySym;
    unsigned char seqState;

    /* 
     * Cached display information
     */
    const unsigned char *RssiBarTable;
    int NRssiBars;

    #if CFG_VOIP_VOICE_MAIL_ENABLE()
    int LastNVoiceMailMsgs;
    #endif
};

/*===========================================================================*/
/*==============  Data      =================================================*/
/*===========================================================================*/

/*private*/ struct uiWlanState uiWlanS;

/*
 * Lookup table for the rssi threshold, indexed by icon setting number.
 */
static const unsigned char uiWlanIcon2Rssi5Bars[] = 
    CFG_WLAN_RSSI_ICON_LEVELS_5BARS;
static const unsigned char uiWlanIcon2Rssi3Bars[] = 
    CFG_WLAN_RSSI_ICON_LEVELS_3BARS;

/*===========================================================================*/
/*==============  Menu Code =================================================*/
/*===========================================================================*/

/* called when we pop out of scanning menu  */
/*private*/ void uiWlanStateScanClean(void)
{
    /* Any and all of the following may be overkill in some cases,
     * but won't hurt.
     */
    uiWlanS.scanMenuHandle = NULL;
    uiWlanS.doScanning = 0;
    pollerTaskStop(&uiWlanS.scanTimeoutTask);  
    pollerTaskStop(&uiWlanS.screenRefreshTask);
    nethwWlanScanStop();
    pollerTaskStop(&uiWlanS.wpsChoiceTimerTask);
}

/*private*/ void uiWlanStateScanLow(void)
{
    unsigned Flags = 0;
    #if CFG_WLAN_SIMPLE_CONFIG() 
    switch ( uiWlanS.SimpleConfigMode ) {
        case uiWlanState_SimpleConfigMode_ePushButton :
            Flags = nethwWlanScanFlag_Wps;
        break;
        case uiWlanState_SimpleConfigMode_ePIN :
            Flags = nethwWlanScanFlag_Wps;
            Flags |= nethwWlanScanFlag_WpsPinOnly;
        break;
        case uiWlanState_SimpleConfigMode_eOff :
        default:
            assert(uiWlanS.SimpleConfigMode == 
                uiWlanState_SimpleConfigMode_eOff);
        break;
    }
    #endif
    nethwWlanScanStart(Flags);
}

/*private*/ void uiWlanStateScanStart(void)
{
    uiWlanS.doScanning = 1;
    uiWlanS.networkCount = 0;
    uiWlanStateScanLow();
    pollerTaskLongTimeoutRelSeconds(&uiWlanS.scanTimeoutTask, 
        UI_WLAN_SCAN_TIMEOUT_SECONDS );
    /* On timeout, separate code will call nethwWlanScanStart() again */
    pollerTaskLongTimeoutRelSeconds(&uiWlanS.screenRefreshTask, 
        UI_WLAN_SCAN_SCREEN_REFRESH_SECONDS );
}

/*
 * Sort based on rssi (decending... so best sources are first),
 * and then by ssid.  Put hidden ssid at the end.
 */
static int uiWlanStateScanCompare(nethwWlanNetworkInfo **p1, nethwWlanNetworkInfo **p2)
{
    if ( (*p1)->rssi != (*p2)->rssi )
        return (*p2)->rssi - (*p1)->rssi;

    if ( (*p1)->ssid[0]  && !(*p2)->ssid[0] ) return 1;
    if ( !(*p1)->ssid[0] &&  (*p2)->ssid[0] ) return -1;
    return strcasecmp((*p1)->ssid, (*p2)->ssid);
}


#if CFG_WLAN_SIMPLE_CONFIG() 
/*
 * Return nonzero if simpleconfig is ready to happen for this AP
 */
/*private*/ int uiWlanCheckScanIsSimpleConfigReady(
        const nethwWlanNetworkInfo *networkP)
{
    /* TODO cache this info for better speed */
    return simpleconfigIsSimpleConfigReady(networkP->beacon.ie_wps);
}
#endif

#if CFG_WLAN_SIMPLE_CONFIG() 
/*
 * Return nonzero if simpleconfig is ready to happen in push button mode
 * for this AP
 */
/*private*/ int uiWlanCheckScanIsSimpleConfigPushButton(
        const nethwWlanNetworkInfo *networkP)
{
    /* TODO cache this info for better speed */
    return simpleconfigIsSimpleConfigPushButton(networkP->beacon.ie_wps);
}
#endif


/*
 * Fetch the current scan list and sort it.
 * This is / should be done ONLY on new scan results!!!!!!
 */
/*private*/ void uiWlanStateScanGetSort(void)
{
    uiWlanS.networkCount = 0;
    uiWlanS.menuNetCurr = 0;
    for ( int i = 0 ; i < CFG_WLAN_SCAN_MAX ; i++ ) {
        const nethwWlanNetworkInfo *networkP = wlanScanScanGet(i);
        if ( networkP == NULL ) continue;
        #if CFG_WLAN_SIMPLE_CONFIG() 
        /* In SimpleConfig mode, show only those ready for SimpleConfig */
        if ( uiWlanS.SimpleConfigMode == 
                uiWlanState_SimpleConfigMode_ePIN ) {
            if ( uiWlanCheckScanIsSimpleConfigReady(networkP) &&
                    uiWlanCheckScanIsSimpleConfigPushButton(networkP) == 0 ) {
                debugf("uiWlanStateScanGetSort: IS simpleconfig ready (PIN): %s",
                    networkP->ssid );
            } else {
                continue;
            }
        } 
        if ( uiWlanS.SimpleConfigMode == 
                uiWlanState_SimpleConfigMode_ePushButton ) {
            if ( uiWlanCheckScanIsSimpleConfigReady(networkP) &&
                    uiWlanCheckScanIsSimpleConfigPushButton(networkP) ) {
                debugf("uiWlanStateScanGetSort: IS simpleconfig ready (PB): %s",
                    networkP->ssid );
            } else {
                continue;
            }
        } 
        #endif
        /* For stability, we make a copy! 
         *      Otherwise, the gui changes mid-flight which
         *      is disconcerting.
         */
        uiWlanS.networkCopy[uiWlanS.networkCount] = *networkP;
        uiWlanS.networkP[uiWlanS.networkCount] = 
            &uiWlanS.networkCopy[uiWlanS.networkCount];
        uiWlanS.networkCount++;
    }
    
    if ( uiWlanS.networkCount == 0 ) {
        uiWlanS.menuNetCurr = 0;
        return;
    }
    /* This sort is actually redundant since wlanScan sorts...
     * although it doesn't do the subsort by ssid, only the main
     * sort by rssi.
     */
    qsort(uiWlanS.networkP, uiWlanS.networkCount,
                sizeof(uiWlanS.networkP[0]),
                (void*)uiWlanStateScanCompare);

#if 0
    debugf("uiWlanStateScanGetSort: after sort, cnt = %d, curr = %d:\n",
                uiWlanS.networkCount, uiWlanS.menuNetCurr);
    for ( int i = 0 ; i < uiWlanS.networkCount ; i++ ) {
        networkP = uiWlanS.networkP[i];

        if ( !networkP->used ) continue;
        debugf("  Index %d, SSID %s chan %d rssi %d\n",
                i, networkP->ssid, networkP->chanNumber, networkP->rssi);
    }
#endif
}

#if CFG_WLAN_SIMPLE_CONFIG() 
static void uiWlanStateSimpleConfigCleanup(void *unused)
{
    simpleconfigStop();
    wlanProfileSimpleConfigCancel();    /* just in case */
}
#endif



/*-F- uiSimpleconfigStatusGet -- called from ui for updates.
 *      The format of the args matches that needed for "menu" status updates.
 */
void uiSimpleconfigStatusGet(
        char msg[],     /* NULL or output: status message to show */
        int msgMax,     /* sizeof(msg[]) */
        int *failureP,  /* NULL or output: nonzero on failure */
        int *successP   /* NULL or output: nonzero on success */
        )
{
    if ( msg ) msg[0] = 0;
    if ( failureP ) *failureP = 0;
    if ( successP ) *successP = 0;

    int Step = 0;
    int MaxStep = 0;
    avmerrCode err = 0;
    simpleconfigStatusCheck( &Step, &MaxStep, &err);
    if ( MaxStep == 0 ) return;         /* configured out */
    if ( Step == MaxStep ) {
        if ( successP ) *successP = 1;
        if ( msg) sprcpy(msg, msgMax, langGet(LANG_SoftKey_Done));
        return;
    }
    if ( Step == 0 ) {
        /* not started or failed */
        if ( err ) {
            if ( failureP ) *failureP = 1;
            sprcpy(msg, msgMax, langErrGet(err));
            return;
        }
        if ( msg) sprcpy(msg, msgMax, langGet(LANG_Dialog_text_error));
        if ( failureP ) *failureP = 1;
        return;
    }
    /* TODO: replace this simple numerical stage indicator
     *  with something better
     */
    if ( msg) spr(msg, msgMax, "%d/%d", Step, MaxStep );
}





#if CFG_WLAN_SIMPLE_CONFIG() 
static void uiWlanStateSimpleConfigDone(void *unused)
{
    /* We are called for both SUCCESS and FAILURE cases!
     *  In case of success, we are done... we need to pop.
     *  in case of failure, this is for retry.
     */
    int Success = 0;
    uiSimpleconfigStatusGet( NULL, 0, NULL, &Success );
    wlanProfileSimpleConfigCancel();
    if ( Success ) {
        uiMenuPop(1);
    } else {
        uiMenuPop(0);
        extern void uiWlanStateSimpleConfig( void *data );  /* forward */
        uiWlanStateSimpleConfig(NULL);  /* start over */
    }
}
#endif

#if CFG_WLAN_SIMPLE_CONFIG() 
static void uiWlanStateDialogUpdate(
        char *msg,              /* output */
        int msgMax,
        enum uiMenuStatusStateEnum *stateP      /* output */
        )
{
    int Failure = 0;
    int Success = 0;
    uiSimpleconfigStatusGet( msg, msgMax, &Failure, &Success );
    if ( Failure ) *stateP = uiMenuStatusState_eFailure;
    else
    if ( Success ) *stateP = uiMenuStatusState_eDone;
    else *stateP = uiMenuStatusState_eBusy;
}
#endif

#if CFG_WLAN_SIMPLE_CONFIG() 
static void uiWlanStateSimpleConfigStatusStart(void)
{
    /* Bring up status window. This just shows status periodically,
     *  and allows cancelling.
     */
    UIMenuStatusInit init = {};
    init.title.textIdx   = LANG_Menu_Setup_wlan_autoconfig_in_progress_title;
    init.selectCB = uiWlanStateSimpleConfigDone;
    init.cleanupCB = uiWlanStateSimpleConfigCleanup;
    init.updateCB = uiWlanStateDialogUpdate;
    init.updateSeconds = 2;     /* refresh status on screen periodically */
    // http hooks  won't work when doing SimpleConfig
    //  since the network will be down... so don't worry about them.
    uiMenuStatusInit(&init);
}
#endif


#if CFG_WLAN_SIMPLE_CONFIG() 
void uiWlanSimpleConfigCB(void)
{
    /* doing a repaint will take care of things */
    uiMenuRefresh();
}
#endif


/*
 * Here for auto-configuration using "SimpleConfig" protocol.
 * set it's ssid as the current ssid to use...
 * Note that uiMenuPop(0) has already been called.
 */
#if CFG_WLAN_SIMPLE_CONFIG() 
/*private*/ void uiWlanStateSelectedSimpleConfig(void)
{
    if ( uiWlanS.menuNetCurr >= uiWlanS.networkCount ) goto Abort;
    nethwWlanNetworkInfo *networkP = uiWlanS.networkP[uiWlanS.menuNetCurr];
    if ( uiWlanS.SimpleConfigMode == 
            uiWlanState_SimpleConfigMode_ePushButton ) {
        /* Don't use the PIN we generated */
        uiWlanS.SimpleConfigPin[0] = 0; /* means use push button method */
        /* WPS spec insists that there be no other push button
         * cases.
         */
        if ( uiWlanS.networkCount > 1 ) {
            uiMenuDialogMesgTimed(
                LANG_err_SimpleConfig_err,
                langGet(LANG_err_SimpleConfig_toomany)
                );
            return;
        } 
    }
    /* FIX! we are using the displayable form of ssid, but in theory
     *  at least this may differ from the raw ssid...
     *  probably users of wlanProfile should do a conversion from
     *  displayable form to raw form as needed?
     */
    const char *ssid =  networkP->ssid;
    if ( ! ssid[0] ) {
        debugf("uiWlanStateSelectedSimpleConfig: hidden ssid not allowed");
        goto Abort;
    }
    /* wlanProfile needs some help because the IP profile needs
     * to be restarted also.
     */
    lwipProfileSetDefaults();   /* clean defaults */
    /* "data" is NULL for empty PIN or else PIN string
     *      (possibly empty)... empty PIN means use button method.
     *
     *  wlanProfileSimpleConfigStart will callback (if connected)
     *  to networking code that will start simple config...
     *  if no callback installed, it will do nothing.
     */
    simpleconfigStart(ssid, uiWlanS.SimpleConfigPin, uiWlanSimpleConfigCB);
    uiWlanStateSimpleConfigStatusStart();
    return;

    Abort:
    /* Activate previous menu (we previously did uiMenuPop(0)) */
    uiMenuActivate();
    return;
}
#endif


/*
 * Here for manual config based upon selected scan.
 * set it's ssid as the current ssid to use...
 */
static void uiWlanStateSelectedManual(void *data)
{
    if ( uiWlanS.menuNetCurr >= uiWlanS.networkCount ) return;
    #if 0
    uiMenuPop(0);       /* pop back out to give feedback */
    #endif
    nethwWlanNetworkInfo *networkP = uiWlanS.networkP[uiWlanS.menuNetCurr];
    /* FIX! we are using the displayable form of ssid, but in theory
     *  at least this may differ from the raw ssid...
     *  probably users of wlanProfile should do a conversion from
     *  displayable form to raw form as needed?
     */
    const char *ssid =  networkP->ssid;
    int iProfile = wlanProfileGetBySsid(ssid);
    struct wlanProfile *profile0 = wlanProfileGetCurrent();
    if ( iProfile == 0 ) {
        /* current profile matches */
        goto GotIt;
    }
    if ( networkP->SecurityMode == wlanProfileSecurityMode_eOpen ) {
        iProfile = -1;  /* no point in restoring for open mode */
    }
    if ( iProfile < 0 ) {
        /* no match */
        wlanProfileDefaults(profile0);
        if ( ssid[0] ) sprcpy(profile0->ssid, sizeof(profile0->ssid), ssid);
        /* start with clean IP config */
        lwipProfileSetDefaults();
        goto GotIt;
    }
    /* Here we matched a saved one (not current one); restore it.
     *  There could be more than one stored (including "current" one),
     *  so don't make any promises to user but just surprise them.
     */
    /* Do IP parameters before doing wlan, so wlan callbacks will see both.
     */
    {
        lwipProfileRestore(iProfile);
        struct wlanProfile *profile = wlanProfileGet(iProfile);
        struct wlanProfile *profile0 = wlanProfileGetCurrent();
        memcpy( profile0, profile, sizeof(*profile) );
    }
    goto GotIt;

    GotIt:
    // copy information we obtained from the scan
    profile0->networkType = networkP->NetworkType;
    if ( networkP->SecurityMode < wlanProfileSecurityMode_NModes )
        profile0->securityMode = networkP->SecurityMode;
    // and drop into menu for editing ssid
    uiWlanProfileMenuStart(NULL);
    uiWlanProfileChangeStart((void *)0/*current*/);
    uiWlanProfileEditSsidStart(NULL);
    /* do wlanProfileSetCurrent() last so that it's callbacks
     *      see the IP changes as well.
     */
    uiWlanProfileChanged(0/* pop menu*/);
}

#if 0   // was
/* 
 * The following menu is used after selecting a ssid from the scan list.
 */
static const UIMenuNode uiWlanStateSelectedNodes[] = {
    {
        .text        = {LANG_Menu_Setup_wlan_no_config},
        .selectCB    = (UICBSelect*)uiWlanStateSelectedOpenSystem,
    },
    {
        .text        = {LANG_Menu_Setup_wlan_manual_config},
        .selectCB    = (UICBSelect*)uiWlanStateSelectedManual,
    },
};
static UIMenuListInit uiWlanStateSelectedMenu = {
    /*
     * Setup menu title
     */
    .title = { LANG_Menu_Setup_wlan_selected_title },
    /*
     * Number of menu nodes
     * OVERRIDDEN BELOW
     */
    .nNodes = sizeof(uiWlanStateSelectedNodes) / 
                sizeof(uiWlanStateSelectedNodes[0]),
    /*
     * Current node.  0 => top
     */
    .currNode = 0,
    /*
     * Display lines.  0 => dynamic
     */
    .nDispLines = 0,
    /*
     * Menu nodes
     */
    .nodes = uiWlanStateSelectedNodes
};
#endif


/*
 * Here when a given scan is selected... 
 */
static void uiWlanStateSelected(void *data)
{
    pollerTaskStop(&uiWlanS.wpsChoiceTimerTask);        /* in case needed */
    /* If selected when nothing displayed, do nothing */
    if ( uiWlanS.menuNetCurr >= uiWlanS.networkCount ) return;
    uiMenuPop(0);       /* pop back out to give feedback */
    nethwWlanNetworkInfo *networkP = uiWlanS.networkP[uiWlanS.menuNetCurr];
    const char *ssid =  networkP->ssid;
    if ( ssid[0] == 0 ) return; /* don't support hidden ssid */
    #if CFG_WLAN_SIMPLE_CONFIG()
    if ( uiWlanS.SimpleConfigMode ) {
        uiWlanStateSelectedSimpleConfig();
        return;
    }
    #endif
    #if 0       // was
    uiMenuListInit(&uiWlanStateSelectedMenu);
    #else
    uiWlanStateSelectedManual(NULL);
    #endif
}

static void uiWlanStateScanListWinRepaint(
        UIMenu *menu,
        Pw_GC *gc,
        Pw_Coord y0,
        Pw_Coord y1
    )
{
    debugf("uiWlanStateScanListWinRepaint\n");

    Pw_Window *win = Pw_GCGetWindow(gc);
    Pw_Coord  w, h;
    Pw_Coord  sb_w, sb_y0 = y0, sb_y1 = y1;
    char str[UI_MENU_MAXSTR];

    nethwWlanNetworkInfo *networkP;
    if ( uiWlanS.networkCount == 0 ) {
        uiWlanS.menuNetCurr = 0;
        networkP = NULL;
    } else {
        networkP = uiWlanS.networkP[uiWlanS.menuNetCurr];
    }

    Pw_WindowGetSize(win, &w, &h);
    sb_w = w;
    w -= CFG_SCROLL_BAR_WIDTH;
    int xindent = 3;

    Pw_GCSetFont(gc, dispFontGet(DISP_FONT_TYPE_REGULAR));
    Pw_GCSetColor(gc, pw_black_pixel);

    if ( networkP == NULL ) {
        char Buf[CFG_MENU_MAXSTR];
        if ( uiWlanS.SimpleConfigMode ) {
            sprcpy(Buf, sizeof(Buf), langGet(LANG_WlanScan_SimpleConfigSearch));
        } else {
            int NSoFar = wlanScanProgress();
            spr(Buf, sizeof(Buf), langGet(LANG_WlanScan_empty), NSoFar );
        }
        uiMenuDialogTextLayout(
            Buf,
            gc,
            DISP_FONT_TYPE_REGULAR,
            0,  /* mustFit */
            y0,
            y1,
            0,          /* x */
            w-1,        /* w */
            0   /* drawBox */
            );
        return;
    } 
    const char *ssid = networkP->ssid;
    if ( ! ssid[0] ) ssid = langGet(LANG_WlanScan_ssid_hidden);
    int nLinesUsed;
    nLinesUsed = uiTextLayout( ssid, 2/*max nlines*/,
            gc, DISP_FONT_TYPE_REGULAR, 1/*mustfit*/,
                        y0, y1, xindent/*x0*/, w-xindent-1/*x1*/);
    if ( nLinesUsed < 0 ) {
        (void) uiTextLayout( ssid, 2/*max nlines*/,
            gc, DISP_FONT_TYPE_SMALL, 0/*mustfit*/,
                        y0, y1, xindent/*x0*/, w-xindent-1/*x1*/);
    }

    Pw_GCSetFont(gc, dispFontGet(DISP_FONT_TYPE_REGULAR));
    y0 += 2/*max nlines*/ * Pw_GCGetFontHeight(gc);

    spr(str, sizeof(str), langGet(LANG_WlanScan_rssi_val), networkP->rssi);
    Pw_DrawStringAnchor(gc, xindent, y0, str, Pw_TextAnchor_TopLeft);
    Pw_GCSetFont(gc, dispFontGet(DISP_FONT_TYPE_REGULAR));
    y0 += Pw_GCGetFontHeight(gc);

    #if 0       // better used for other information
    spr(str, sizeof(str), langGet(LANG_WlanScan_chan_val), networkP->chanNumber);
    Pw_DrawStringAnchor(gc, xindent, y0, str, Pw_TextAnchor_TopLeft);
    y0 += Pw_GCGetFontHeight(gc);
    #endif

    /* The following attempts to cram information about security mode,
     * network type and WPS into one line.
     */
    str[0] = 0;
    if ( str[0] ) sprcat( str, sizeof(str), " " );
    if ( networkP->SecurityMode < wlanProfileSecurityMode_NModes ) {
        sprcat(str, sizeof(str), 
            langGet(LANG_WlanProfile_SecurityModes + networkP->SecurityMode));
    } else {
        sprcat( str, sizeof(str), 
            langGet(LANG_WlanProfile_SecurityModeUnsupported));
    }
    if ( networkP->NetworkType == wlanProfileNetworkType_eAdhoc ) {
        /* IBSS bit set -- "ad hoc" network */
        if ( str[0] ) sprcat( str, sizeof(str), " " );
        sprcat( str, sizeof(str), langGet(LANG_WlanScan_IBSS));
    }
    if ( networkP->beacon.ie_wps ) {
        struct simpleconfigParseBeaconWpsIeStruct WpsInfo;
        simpleconfigParseBeaconWpsIe( &WpsInfo, networkP->beacon.ie_wps);
        if ( str[0] ) sprcat( str, sizeof(str), " " );
        sprcat( str, sizeof(str), langGet(LANG_WlanScan_WPS));
        if ( WpsInfo.SelectedRegistrar ) {
            /* Here the AP is in active WPS mode, expecting to
             * provide configuration for a client device such as ours.
             * There is not room for everything...
             * show only WPS information in this case
             * (this case is most likely only when the user expects it
             * anyway).
             */
            str[0] = 0;
            if ( WpsInfo.PushButton ) {
                sprcat(str, sizeof(str), 
                    langGet(LANG_WlanScan_WPS_PushButton));
            } else {
                sprcat(str, sizeof(str), 
                    langGet(LANG_WlanScan_WPS_PIN));
            }
        }
    }
    if ( str[0] ) {
        Pw_GCSetFont(gc, dispFontGet(DISP_FONT_TYPE_SMALL));
        Pw_DrawStringAnchor(gc, xindent, y0, str, Pw_TextAnchor_TopLeft);
        y0 += Pw_GCGetFontHeight(gc);
        
    }

    /*
     * TODO: display whether we are connected to this network and
     * whether it requires encryption and whether it supports simpleconfig
     */

    uiMenuScrollBarDraw(gc, sb_w, sb_y0, sb_y1,
                        uiWlanS.menuNetCurr, uiWlanS.networkCount);
    return;
}


static void uiWlanScreenRefreshHandler( struct pollerTask  *TaskP )
{
    uiMenuRefresh();    /* schedule refresh of screen */
    if ( uiWlanS.doScanning ) {
        pollerTaskLongTimeoutRelSeconds(&uiWlanS.screenRefreshTask, 
            UI_WLAN_SCAN_SCREEN_REFRESH_SECONDS );
    }
    return;
}


static Pw_Bool uiWlanStateScanListWinEvents(UIMenu *menu, Pw_Event *ev)
{
    Pw_Window* win;
    Pw_Coord x, y;

    win = Pw_EventGetWindow(ev);

    Pw_WindowGetPosition(win, &x, &y);

    switch (Pw_EventGetType(ev))
    {
        case Pw_KeyPressEventType: {
            Pw_KeyEvent* kev = Pw_EventCastToKeyEvent(ev);
            Pw_uInt keycode = Pw_KeyEventGetKeyCode(kev);

            switch ( keycode ) {

                case KEYSYM_TALK:
                break;

                case KEYSYM_END:
                    /* pop the menu stack */
                    uiMainWinKeyEnd();
                return TRUE;

                case KEYSYM_DOWN:
                    if ( uiWlanS.networkCount == 0 ) {
                        return TRUE;
                    }
                    uiWlanS.menuNetCurr++;
                    if ( uiWlanS.menuNetCurr >= uiWlanS.networkCount ) {
                        uiWlanS.menuNetCurr = 0;
                    }
                break;

                case KEYSYM_UP:
                    if ( uiWlanS.networkCount == 0 ) {
                        return TRUE;
                    } else if ( uiWlanS.menuNetCurr == 0 ) {
                        uiWlanS.menuNetCurr = uiWlanS.networkCount - 1;
                    } else {
                        uiWlanS.menuNetCurr--;
                    }
                break;

                case KEYSYM_LEFT:
                case KEYSYM_CLEAR:
                case KEYSYM_SOFT_NEGATIVE:
                    if ( uiWlanS.doScanning ) {
                        uiWlanS.doScanning = 0; /* no more scanning! */
                        nethwWlanScanStop();  /* will trigger callback */
                    } else {
                        uiMenuPop(1);
                    }
                return TRUE;

                case KEYSYM_SELECT:
                case KEYSYM_RIGHT:
                case KEYSYM_SOFT_POSITIVE:
                    /*
                     * display list of options for this network
                     */
                    if ( uiWlanS.networkCount == 0 ) {
                        return TRUE;
                    }
                    uiWlanStateSelected((void*)
                                           (unsigned)uiWlanS.menuNetCurr);
                return TRUE;
                
                case KEYSYM_2:
                case KEYSYM_3:
                case KEYSYM_4:
                case KEYSYM_5:
                case KEYSYM_6:
                case KEYSYM_7:
                case KEYSYM_8:
                case KEYSYM_9: {
                    if ( uiWlanS.networkCount == 0 ) {
                        return TRUE;
                    }
                    /* 
                     * Implement a simple ssid search by jumpiug
                     * to entries that start with the letter pressed
                     * by the user
                     */
                    unsigned int c;

                    if ( keycode != uiWlanS.lastKeySym ) {
                        /* 
                         * Different key: reset state and keySym
                         */
                        uiWlanS.seqState   = 0;
                        uiWlanS.lastKeySym = keycode;
                    }

                    /*
                     * Get next character in the sequence, skipping digits
                     *
                     * Note: langCharSeq() could return a unicode character
                     * above 0xff, in which case the lookup won't find
                     * anything.
                     */
                    c = langCharSeq(keycode, langPage_eLowerCase, 
                        &uiWlanS.seqState);
                    if ( isdigit(c) )
                        c = langCharSeq(keycode, langPage_eLowerCase, 
                            &uiWlanS.seqState);

                    for ( int i = 0 ; i < uiWlanS.networkCount ; i++ ) {
                        nethwWlanNetworkInfo *networkP = uiWlanS.networkP[i];
                        unsigned char c0 = networkP->ssid[0];

                        if ( isalpha(c0) ) c0 = tolower(c0);
                        if ( c0 >= c ) {
                            uiWlanS.menuNetCurr = i;
                            if ( uiWlanS.menuNetCurr < 0 )
                                uiWlanS.menuNetCurr = 0;
                            break;
                        }
                    }
                }
                break;

                default:
                    debugf("uiWlanStateScanListWinEvents: Got KeyPress %d\n",
                            Pw_KeyEventGetKeyCode(kev));
                break;
            }
            Pw_WindowRepaint(win);
            return TRUE;
        }

        case Pw_KeyReleaseEventType: {
            Pw_KeyEvent* kev = Pw_EventCastToKeyEvent(ev);
            Pw_uInt keycode  = Pw_KeyEventGetKeyCode(kev);

            switch ( keycode ) {

                // case KEYSYM_POWER:
                //     uiPowerKeyRelease();
                //     return TRUE;

                default:
                    return FALSE;
            }
        }

        default:
            return FALSE;
    }
}

/***********************************************************************
 * HTTPD interface routines for displaying network scan list
 ***********************************************************************/

#if CFG_HTTPD_ENABLE

static void uiWlanStateScanListHttpNavGoTo(
        int node, 
        int level, 
        int topLevel,
        int doOpen
        )
{
    if ( node < 0 || node >= uiWlanS.menuNetCurr ) {
        /*
         * Invalid node - pop the menu stack
         */
        uiMenuPopLevel(level, 1);
    } else if ( node != uiWlanS.menuNetCurr ) {
        /*
         * Different entry; pop the menu stack
         * and push on the new entry.
         */
        uiWlanS.menuNetCurr = node;
        if ( doOpen ) {
            uiMenuPopLevel(level, 0);
            uiWlanStateSelected(NULL);
        }
    }
}

/* Emit navigation link that lives on left side of web page */
static void uiWlanStateScanListHttpNavLink(
        struct _UIMenu *menu, HttpdOutput *output,
        char *parentUrl, int level)
{
    char   url[UI_MENU_MAXSTR];
    for ( int i = 0 ; i < uiWlanS.networkCount ; i++ ) {
        nethwWlanNetworkInfo *networkP = uiWlanS.networkP[i];
        if ( ! networkP ) continue;
        spr(url, sizeof(url), "%s/%d", parentUrl, i);
        if ( i == uiWlanS.menuNetCurr ) {
            uiHttpNavLink(output, networkP->ssid, url, 1/*selected*/, level);
            uiMenuHttpNavLink(output, url, level+1);
        } else {
            uiHttpNavLink(output, networkP->ssid, url, 0/*! selected*/, level);
        }
    }
}


static void uiWlanStateScanListHttpContent(HttpdOutput *output, char *url)
{
    /*
     * Display the entire network scan list
     * TODO: call pollerYield() inside the loop since it
     * could take a long time.
     */
    uiWlanS.networkCount = 0;

    httpdOutSpr(output, "<table border=\"1\">\n");
    httpdOutSpr(output, "<tr><td></td><td>SSID</td><td>RSSI</td><td>Chan</td></tr>\n");
    for ( int i = 0 ; i < uiWlanS.networkCount ; i++ ) {
        nethwWlanNetworkInfo *networkP = uiWlanS.networkP[i];
        if ( ! networkP ) continue;
        httpdOutSpr(output, "<tr>");
        httpdOutSpr(output, 
            "<td><input type=\"radio\" name=\"v\" value=\"%d\"%s></td>",
                i, 
                (i == uiWlanS.menuNetCurr ? " checked" : "") );
        httpdOutSpr(output, "<td><a href=\"%s/%d/menu.html\">%s</a></td>",
                        url, i, networkP->ssid );
        httpdOutSpr(output, "<td>%d</td><td>%d</td>",
                        networkP->rssi, networkP->chanNumber);
        httpdOutSpr(output, "</tr>\n");
    }
    httpdOutSpr(output, "</table>\n");
}

#endif /* CFG_HTTPD_ENABLE */

/***********************************************************************
 * END HTTPD interface routines 
 ***********************************************************************/



/*
 * Display list of WLAN networks
 */
void uiWlanStateScanListMenuStart(
        void *data              /* 0=normal search, 1,2=simpleconfig modes */
        )
{
    /* Remember if we are to show all APs or only those
     *  that advertise a simpleconfig (WPS)  mode being ready.
     */
    #if CFG_WLAN_SIMPLE_CONFIG() 
    uiWlanStateSimpleConfigCleanup(NULL);   /* just in case, can happen */
    uiWlanS.SimpleConfigMode = (int)data;
    if ( uiWlanS.SimpleConfigMode ) {
        uiMenuPop(0);
    }
    #endif

    UIMenuUserInit init = {};

    init.title.textIdx  = LANG_WlanScan_title;
    init.eventHandler   = uiWlanStateScanListWinEvents;
    init.repaintHandler = uiWlanStateScanListWinRepaint;
    init.cleanupCB      = (void *)uiWlanStateScanClean;

#if CFG_HTTPD_ENABLE

    init.httpNavGoTo  = uiWlanStateScanListHttpNavGoTo;
    init.httpNavLink  = uiWlanStateScanListHttpNavLink;
    init.httpContent  = uiWlanStateScanListHttpContent;
    init.httpSubmit   = NULL;

#endif /* CFG_HTTPD_ENABLE */

    uiMenuSoftKeyNegative(init.softKey, LANG_SoftKey_Stop);
    uiMenuSoftKeyPositive(init.softKey, LANG_SoftKey_Select);

    uiWlanS.scanMenuHandle = uiMenuUserInit(&init, NULL);
    uiWlanStateScanStart();
    return;
}


#if CFG_WLAN_SIMPLE_CONFIG() 
void uiWlanStateSimpleConfigPinEntryDone(
        void *arg,
        const char *str
        )
{
    if ( simpleconfigPinCheck(str) ) {
        uiMenuDialogMesgTimed(
            LANG_WlanScan_SimpleConfigPin_Invalid,
            langGet(LANG_WlanScan_SimpleConfigPin_Invalid) );
        return;
    }
    /* Use this PIN (presumeably copied from AP) instead of one we
     * generated.  So long as both sides use same PIN then we're ok.
     */
    sprcpy( uiWlanS.SimpleConfigPin, sizeof(uiWlanS.SimpleConfigPin), str);
    uiWlanStateScanListMenuStart((void *)uiWlanState_SimpleConfigMode_ePIN);
}
#endif

#if CFG_WLAN_SIMPLE_CONFIG() 
/* Here to get a PIN that the user copied off of the AP
 *      (for APs that provide the PIN; Atheros APs expect
 *      that the client provide the PIN).
 */
void uiWlanStateSimpleConfigPinEntryStart( void *data )
{
    UIMenuNumEntryInit init = {};
    init.title.textIdx    = LANG_WlanScan_SimpleConfigModeChoice_PinFromAp,
    init.prompt.textIdx   = LANG_WlanScan_SimpleConfigModeChoice_PinFromAp_Prompt,
    init.doneCB          = uiWlanStateSimpleConfigPinEntryDone;
    init.bufSize         = 8+1;   /* should be exactly 8 digits entered*/
    // init.cursorPos       = strlen(init.str);
    uiMenuNumEntryInit(&init);
}
#endif

#if CFG_WLAN_SIMPLE_CONFIG() 
void uiWlanStateSimpleConfigPinTextCB(
    char *buf, int bufsize, 
    LangIdx text, int index, void *arg)
{
    spr(buf, bufsize,
        langGet(LANG_WlanScan_SimpleConfigModeChoice_PIN),
        uiWlanS.SimpleConfigPin
        );
}
static const UIMenuNode SimpleConfigModeNodes[] = {
    {
        .text        = {LANG_WlanScan_SimpleConfigModeChoice_button},
        .selectCB    = (UICBSelect*)uiWlanStateScanListMenuStart,
        .selectCBarg = (void *)uiWlanState_SimpleConfigMode_ePushButton
    },
    {
        .text        = { .textCB = uiWlanStateSimpleConfigPinTextCB },
        .selectCB    = (UICBSelect*)uiWlanStateScanListMenuStart,
        .selectCBarg = (void *)uiWlanState_SimpleConfigMode_ePIN
    },
    {
        .text        = {LANG_WlanScan_SimpleConfigModeChoice_PinFromAp},
        .selectCB    = (UICBSelect*)uiWlanStateSimpleConfigPinEntryStart,
        .selectCBarg = NULL
    },
};
static const UIMenuListInit MenuSimpleConfigMode = {
    .title = { LANG_Menu_SimpleConfigMode_title },
    .nNodes = sizeof(SimpleConfigModeNodes) / sizeof(SimpleConfigModeNodes[0]),
    .currNode = 0,
    .nDispLines = 0,
    .nodes = SimpleConfigModeNodes,
};
#endif  // CFG_WLAN_SIMPLE_CONFIG() 


#if CFG_WLAN_SIMPLE_CONFIG() 
/*
 * User has selected SimpleConfig (WPS)
 */
void uiWlanStateSimpleConfig(
        void *data
        )
{
    /* Generate a PIN (secret number) in case it turns out
     * we need it.
     */
    simpleconfigPinGenerate(uiWlanS.SimpleConfigPin);

    /* Enter simpleconfig mode with no parameters yet.
     *  This is to assure against sending any packets that will
     *  interfere once the button has been pushed.
     */
    wlanProfileSimpleConfigStart( "", "" );

    /* Bring up a menu to select push button or pin mode.
     * The idea is that the user will actually either push the AP's
     * button or enter PIN into the AP before continuing!
     */
    uiMenuListInit(&MenuSimpleConfigMode);
}
#endif  // CFG_WLAN_SIMPLE_CONFIG() 

/*===========================================================================*/
/*==============  WLAN State Code ===========================================*/
/*===========================================================================*/

static void uiWlanStateRssiIconUpdate(
        struct pollerTask *TaskP        /* ignored! may pass as NULL */
        )
{
    /* In order to support different display types, the number of bars
     * (and thus the translation table) must be dynamically determined.
     */
    if ( ! uiWlanS.RssiBarTable ) {
        uiWlanS.NRssiBars = dispIconNBarsGet( DISP_ICON_SIGNAL_LEVEL );
        switch ( uiWlanS.NRssiBars ) {
            case 3:
                uiWlanS.RssiBarTable = uiWlanIcon2Rssi3Bars;
            break;
            case 5:
                uiWlanS.RssiBarTable = uiWlanIcon2Rssi5Bars;
            break;
            default:
                /* Need another table? */
            return;
        }
    }
    const unsigned char *table = uiWlanS.RssiBarTable;
    int maxbars = uiWlanS.NRssiBars;

    int rssiIcon = -1;  /* -1 for invalid; don't even display antenna */

    if ( uiWlanS.connected ) {
        uiWlanS.connRssi = nethwWlanRssiGet();

        #if 0 /* rssi changes tend to be dramatic; this is pointless? */
        /*
         * Look up the rssi so get the icon display.
         * We use some hysterisis so very small rssi changes
         * don't cause too many changes in the icon display.
         */
        if ( uiWlanS.connRssiIcon >= 0 ) {
            int rssiLow  = 0, rssiHigh = 255;

            if ( uiWlanS.connRssiIcon > 0 ) {
                /*
                 * +1 below because table limits are low < rssi <= high
                 * ie: inclusive at the high but not at the low.
                 */
                rssiLow = table[uiWlanS.connRssiIcon - 1]
                                        - CFG_WLAN_RSSI_ICON_HYSTERISIS + 1;
            }
            if ( uiWlanS.connRssiIcon < maxbars ) {
                rssiHigh = table[uiWlanS.connRssiIcon]
                                        + CFG_WLAN_RSSI_ICON_HYSTERISIS;
            }
#if 0
            debugf("uiWlanStateRssiIconUpdate: icon = %d, rssi = %d, [%d,%d]\n",
                                uiWlanS.connRssiIcon, uiWlanS.connRssi,
                                rssiLow, rssiHigh);
#endif

            if ( rssiLow <= uiWlanS.connRssi && uiWlanS.connRssi <= rssiHigh ) {
                /*
                 * RSSI is still within the current band, including
                 * the CFG_WLAN_RSSI_ICON_HYSTERISIS
                 */
                return;
            }
        }
        #endif
        int nbars = maxbars;
        for ( ; nbars > 0 ; nbars-- ) {
            if ( uiWlanS.connRssi > table[nbars-1] ) {
#if 0
                debugf("uiWlanStateRssiIconUpdate: rssi %d > %d (index %d)\n",
                                uiWlanS.connRssi,
                                table[nbars-1], nbars-1);
#endif
                break;
            }
        }
        rssiIcon = nbars;
    }

    if ( uiWlanS.connRssiIcon != rssiIcon ) {
        uiWlanS.connRssiIcon = rssiIcon;
        dispIconSet(uiDisplay(), DISP_ICON_SIGNAL_LEVEL, uiWlanS.connRssiIcon);
    }


    /* This doesn't really belong here exactly, but is a convenient place:
     * display voice mail indicator 
     */
    #if CFG_VOIP_VOICE_MAIL_ENABLE()
    int NewMsgs = 0;
    callCtrlVoiceMailWaitingGet(&NewMsgs,NULL,NULL,NULL);
    if ( uiWlanS.LastNVoiceMailMsgs != NewMsgs ) {
        debugf("Got %d new voice mail messages", NewMsgs );
        uiWlanS.LastNVoiceMailMsgs = NewMsgs;
        dispIconSet(uiDisplay(), DISP_ICON_VOICE_MAIL, (NewMsgs != 0));
    }
    #endif      // CFG_VOIP_VOICE_MAIL_ENABLE()

    #if CFG_WLAN_RSSI_ICON_UPDATE_SECONDS() > 0
    pollerTaskLongTimeoutRelSeconds( 
        &uiWlanS.iconTimerTask,
        CFG_WLAN_RSSI_ICON_UPDATE_SECONDS() );
    #endif
}

/*
 * This function is called whenever wlanEtna reports a state
 * change event, such as a connect or disconnect event.
 */
static void uiWlanStateEventHandler(
        struct pollerTask *taskP
    )
{
    unsigned short connFreq;
    unsigned char connBssid[6];

    /*
     * Check connected state
     */
    int connected = nethwWlanConnectionGet(&connFreq, connBssid, NULL, NULL);

    if ( uiWlanS.connected == connected ) {
        /*
         * check if we have roamed to a new bssid
         */
        if ( connected && (memcmp(uiWlanS.connBSSID,
                                 connBssid, sizeof(uiWlanS.connBSSID))
                        || uiWlanS.connFreq != connFreq) ) {
            memcpy(uiWlanS.connBSSID, connBssid, sizeof(uiWlanS.connBSSID));
            uiWlanS.connFreq = connFreq;
        }
    } else {
        if ( connected ) {
            /*
             * disconnected -> connected
             */
            memcpy(uiWlanS.connBSSID, connBssid, sizeof(uiWlanS.connBSSID));
            uiWlanS.connFreq = connFreq;
            /*
             * TODO: display dialog?
             */
            debugf("uiWlanStateEventHandler: connected to bssid 0x%02x:%02x:%02x:%02x:%02x:%02x, connFreq %d\n",
                connBssid[0],
                connBssid[1],
                connBssid[2],
                connBssid[3],
                connBssid[4],
                connBssid[5],
                connFreq);
        } else {
            /*
             * connected -> disconnected
             */
        }
        uiWlanS.connected = connected;
        uiWlanStateRssiIconUpdate(NULL);

    }
}

/*
 * This function is called if user doesn't proceed with WPS
 * within a timeout after the WPS scan is done.
 * This is necessary because the scan information becomes stale,
 * creating a security hole (see WPS specification).
 */
/*private*/ void uiWlanStateWpsChoiceTimeoutHandler(
        struct pollerTask *taskP
        )
{
    if ( uiWlanS.scanMenuHandle && uiWlanS.SimpleConfigMode &&
            uiWlanS.doScanning == 0 ) {
        #if 0   /* this could really run down the battery */
        /* Go back to scanning again.... */
        debugf("uiWlanStateWpsChoiceTimeoutHandler restart scanning");
        uiWlanStateScanStart();
        #else
        uiMenuPopLevelOnly(uiWlanS.scanMenuHandle, 0/*activate*/);
        uiMenuDialogErrMsg(LANG_err_SimpleConfig_choice_timeout);
        #endif
    }
}


/*
 * This function is called on scanning done event only,
 * AFTER results have been saved in wlanScan.
 */
/*private*/ void uiWlanStateScanEventHandler(
        struct pollerTask *taskP
    )
{
    /* Refresh only if we haven't scanned already...
     *      don't mess up the display with new scan results.
     *      NOTE that doScanning will be 0 if user just wants
     *      to see results now; 1 if they are willing to keep going.
     */
    if ( uiWlanS.scanMenuHandle && uiWlanS.networkCount == 0 ) {
        uiMenuRefresh();    /* schedule update just in case */
        pollerTaskStop(&uiWlanS.scanTimeoutTask);
        pollerTaskStop(&uiWlanS.screenRefreshTask);
        uiWlanStateScanGetSort();
        if ( uiWlanS.networkCount == 0 ) {
            /* Didn't get anything... */
            if ( uiWlanS.doScanning ) {
                /* try again until we get something */
                debugf("uiWlanStateScanEventHandler: did not get anything");
                uiWlanStateScanStart();
            } else {
                /* user was done... give up */
                uiMenuPop(1);
            }
        } else {
            debugf("uiWlanStateScanEventHandler: got %d", 
                uiWlanS.networkCount);
            uiWlanS.doScanning = 0;
            /* TODO: change NEGATIVE soft key label
            * to "Back" now, instead of "Stop".
            */
            /* Protect against using stale WPS information by 
             * timing out if user doesn't respond.
             */
            if ( uiWlanS.scanMenuHandle && uiWlanS.SimpleConfigMode ) {
                pollerTaskLongTimeoutRelSeconds(
                    &uiWlanS.wpsChoiceTimerTask,
                    UI_WLAN_SCAN_WPS_SELECT_TIMEOUT_SECONDS
                    );
            }
        }
    } else {
        debugf("uiWlanStateScanEventHandler: called unexpectedly");
    }
}

/* This function is called if we don't get scan results promptly.
 */
static void uiWlanStateScanTimeoutHandler(
        struct pollerTask *taskP
    )
{
    if ( uiWlanS.scanMenuHandle && uiWlanS.networkCount == 0 ) {
        if ( uiWlanS.doScanning ) {
            nethwWlanScanStop(); /* this will cause wmi_scanComplete callback */
        } else {
            /* just in case... */
            uiMenuPop(1);
        }
    }
}

void uiWlanStateInit(void)
{
    wlanProfileInit();

    mainthreadTaskInit(&uiWlanS.stateEventTask, "wlanStateEventTask",
                       uiWlanStateEventHandler);
    pollerTaskPrioritySet(&uiWlanS.stateEventTask, CONFIG_PRIORITY_UI);
    
    mainthreadTaskInit(&uiWlanS.scanEventTask, "wlanScanEventTask",
                       uiWlanStateScanEventHandler);
    pollerTaskPrioritySet(&uiWlanS.scanEventTask, CONFIG_PRIORITY_UI);

    mainthreadTaskInit(&uiWlanS.wpsChoiceTimerTask, "wpsChoiceTimerTask",
                       uiWlanStateWpsChoiceTimeoutHandler);
    pollerTaskPrioritySet(&uiWlanS.scanEventTask, CONFIG_PRIORITY_UI);

    mainthreadTaskInit(&uiWlanS.scanTimeoutTask, "wlanScanTimeoutTask",
                       uiWlanStateScanTimeoutHandler);
    pollerTaskPrioritySet(&uiWlanS.scanTimeoutTask, CONFIG_PRIORITY_UI);

    mainthreadTaskInit(&uiWlanS.screenRefreshTask, "wlanScreenRefreshTask",
                       uiWlanScreenRefreshHandler );
    
    nethwWlanStateEventTaskSet(&uiWlanS.stateEventTask, &uiWlanS.scanEventTask);
    mainthreadTaskInit(&uiWlanS.iconTimerTask, "wlanIconTimerTask",
                       uiWlanStateRssiIconUpdate);

    uiWlanS.scanMenuHandle = NULL;
    uiWlanS.connected      = 0;
    uiWlanS.connRssiIcon   = 0;
    uiWlanStateRssiIconUpdate(NULL);
}

/*
 * Force WLAN to reconnect. With multiple profiles enabled,
 * this will cycle through the profile list.
 */
void uiWlanStateReconnect(
        void *data      /* unused */
        )
{
    nethwReconnect();
    /* popup timed message so user knows we did something */
    uiMenuDialogMesgTimed(
        LANG_Menu_Setup_wlan_reconnect,
        langGet(LANG_Menu_Setup_wlan_reconnect)
    );
}


/* WLAN Setup menu *********************************************************/


static const UIMenuNode WlanMenuSetupNodes[] = {
    {
        .text        = {LANG_Menu_Setup_wlan_settings},
        .selectCB    = (UICBSelect*)uiWlanProfileMenuStart,
        .selectCBarg = NULL
    },
    {
        .text        = {LANG_Menu_Setup_wlan_search},
        .selectCB    = (UICBSelect*)uiWlanStateScanListMenuStart,
        .selectCBarg = NULL
    },
    #if CFG_WLAN_SIMPLE_CONFIG() 
    {
        .text        = {LANG_Menu_Setup_wlan_autoconfig},
        .selectCB    = (UICBSelect*)uiWlanStateSimpleConfig,
        .selectCBarg = NULL
    },
    #endif
    {
        .text        = {LANG_Menu_Setup_wlan_reconnect},
        .selectCB    = (UICBSelect*)uiWlanStateReconnect,
        .selectCBarg = NULL
    },
};


/* 
 * Referenced from outside this file:
 */
const UIMenuListInit uiWlanStateMenuSetup = {
    /*
     * Setup menu title
     */
    .title = { LANG_Menu_WlanSetup_title },
    /*
     * Number of menu nodes
     */
    .nNodes = sizeof(WlanMenuSetupNodes) / 
                sizeof(WlanMenuSetupNodes[0]),
    /*
     * Current node.  0 => top
     */
    .currNode = 0,
    /*
     * Display lines.  0 => dynamic
     */
    .nDispLines = 0,
    /*
     * Menu nodes
     */
    .nodes = WlanMenuSetupNodes,

    /* IMPORTANT CLEANUP! This is called when we leave the wlan setup
     * menu, and serves as a backup in case it isn't called previously
     * as can happen in some cases 
     */
    #if CFG_WLAN_SIMPLE_CONFIG() 
    .cleanupCB = uiWlanStateSimpleConfigCleanup,
    #endif
};

/*===========================================================================*/
/*==============  End       =================================================*/
/*===========================================================================*/
